package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"reflect"
	"strconv"
	"strings"
)

// ini 配置文件解析器

// mysqlconfig mysqk配置结构体

type MysqlConfig struct {
	Address  string `ini:"address"`
	Port     int    `ini:"port"`
	Username string `ini:"username"`
	Password string `ini:"password"`
}

type RedisConfig struct {
	Host     string `ini:"host"`
	Port     int    `ini:"port"`
	PassWord string `ini:"passWord"`
	Database int    `ini:"database"`
}

type Config struct {
	MysqlConfig `ini:"mysql"`
	RedisConfig `ini:"redis"`
}

func loadIni(FileName string, data interface{}) (err error) {
	////// 文件1读取
	//file, err := os.OpenFile(FileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
	//if err != nil {
	//	fmt.Println("err:", err)
	//}
	//os.ReadFile(*file)
	// 参数校验
	// 传进来的data参数必须是指针类型（是因为需要在函数中对其赋值）
	t := reflect.TypeOf(data)
	fmt.Println(t.Name(), t.Kind())
	if t.Kind() != reflect.Ptr {
		err = errors.New("data should be a pinter.") // 格式化输出返回一个error类型
		return
	}
	// 传进来的参数必须是结构体指针类型（配置文件中有各种键值对操作）
	if t.Elem().Kind() != reflect.Struct {
		err = errors.New("data should be a struct.") // 格式化输出返回一个error类型
		return
	}

	b, err := ioutil.ReadFile(FileName)
	if err != nil {
		return err
	}
	//string(b) // 将字节类型转换成字符串
	lineSlice := strings.Split(string(b), "\r\n")
	fmt.Printf("%v#\n", lineSlice)
	// 一行一行读
	var structName string
	for idx, line := range lineSlice {
		line = strings.TrimSpace(line)
		// 如果空行就跳过
		if len(line) == 0 {
			continue
		}
		// 如果是注释就跳过
		if strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") {
			continue
		}
		if strings.HasPrefix(line, "[") {
			// 如果是[开头就表示节（section)
			if line[0] != '[' || line[len(line)-1] != ']' {
				err = fmt.Errorf("line:%d syntax error", idx+1)
				return
			}
			//这一行首位去掉， 取到中间的内容
			sectionName := strings.TrimSpace(line[1 : len(line)-1])
			if len(sectionName) == 0 {
				err = fmt.Errorf("line:%d syntax error", idx+1)
				return
			}
			// 根据字符串sectionName找到对应结构体
			//v := reflect.ValueOf(sectionName)
			for i := 0; i < t.Elem().NumField(); i++ {
				field := t.Field(i)
				if sectionName == field.Tag.Get("ini") {
					// 说明找到了对应嵌套结构体，把字段名记下来
					structName = field.Name
					fmt.Printf("找到对应的嵌套结构体%s\n", sectionName, structName)
				}
			}
		} else {
			// 如果不是[开头就是=分割键值对
			// 以等号分割这一行，左边是key，等号右边是value
			if strings.Index(line, "=") == -1 || strings.HasPrefix(line, "=") {
				err = fmt.Errorf("line:%d syntax error", idx+1)
				return
			}
			index := strings.Index(line, "=")
			key := strings.TrimSpace(line[:index])
			value := strings.TrimSpace(line[index+1:])
			v := reflect.ValueOf(data)
			sValue := v.Elem().FieldByName(structName) // 拿到嵌套结构体的值信息
			sType := sValue.Type()                     // 拿到嵌套结构体的类型信息
			// 根据strucrname去data里面把对应的结构体给取出来
			//v := reflect.ValueOf(data)
			structObj := v.Elem().FieldByName(structName)
			if structObj.Kind() != reflect.Struct {
				err = fmt.Errorf("data中的%s字段应该是结构体", structName)
				return
			}
			var fieldName string
			var fileType reflect.StructField
			// 遍历嵌套结构体的没一个字段，判断tag是不是等于key
			for i := 0; i < sValue.NumField(); i++ {
				filed := sType.Field(i) // tag信息是存储在类型信息中的
				fileType = filed
				if filed.Tag.Get("ini") == key {
					// 找到对应的字段
					fieldName = fieldName
					break
				}
			}
			//如果key=tag，给这个字段赋值
			// 根据filedName去取出对应的字段
			if len(fieldName) == 0 {
				// 找不到对应字符
				continue
			}
			fileObj := sValue.FieldByName(fieldName)
			// 赋值
			fmt.Println(fileObj.Type().Kind(), fieldName)
			switch fileType.Type.Kind() {
			case reflect.String:
				fileObj.SetString(value)
			case reflect.Int, reflect.Int8, reflect.Int64, reflect.Int32:
				var valueInt int64
				valueInt, err = strconv.ParseInt(value, 10, 64)
				if err != nil {
					err = fmt.Errorf("line:%d syntax error", idx+1)
					return
				}
				fileObj.SetInt(valueInt)
			case reflect.Bool:
				var valueBool bool
				valueBool, err = strconv.ParseBool(value)
				if err != nil {
					err = fmt.Errorf("line:%d syntax error", idx+1)
					return
				}
				fileObj.SetBool(valueBool)
			case reflect.Float64, reflect.Float32:
				var valueFloat float64
				valueFloat, err = strconv.ParseFloat(value, 64)
				if err != nil {
					err = fmt.Errorf("line:%d syntax error", idx+1)
					return
				}
				fileObj.SetFloat(valueFloat)
			}
		}
	}
	return
}

func main() {
	var cfg Config
	var x = new(int)
	fmt.Println(x)
	err := loadIni("./conf.ini", &cfg)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("%#v\n", cfg)
}
